{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "0d154a35-b2b6-46de-87ab-aa6be7c026ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7ca3ea4e-f052-4cbd-94ff-8c0e3785528b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import json\n",
    "import concurrent\n",
    "import contextlib\n",
    "import math\n",
    "import random\n",
    "import threading\n",
    "import retrying\n",
    "import glob\n",
    "\n",
    "import numpy as np\n",
    "from bs4 import BeautifulSoup\n",
    "from selenium import webdriver\n",
    "from selenium.webdriver.common.by import By\n",
    "from selenium.webdriver.common.action_chains import ActionChains\n",
    "from selenium.webdriver.support.ui import WebDriverWait\n",
    "from selenium.webdriver.support import expected_conditions as EC\n",
    "import pandas as pd\n",
    "import regex as re\n",
    "\n",
    "# from concurrent.futures import ThreadPoolExecutor, as_completed\n",
    "import time\n",
    "\n",
    "from selenium.webdriver.support.select import Select\n",
    "from datetime import datetime\n",
    "import traceback\n",
    "\n",
    "from howlongtobeatpy import HowLongToBeat, SearchModifiers\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "plt.rcParams[\"font.family\"] = [\"Smiley Sans\", \"sans-serif\"]\n",
    "plt.rcParams[\"font.size\"] = 15"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ede93264-8b57-4ac1-8839-44fa3b9781dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from main import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ea03a216-df75-46da-8fe0-fc8a25f904fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_colormap_array(n, name=\"gist_rainbow\", int_out=True):\n",
    "    \"\"\"Get colormap numpy array in n points. Returns in RGB [0,1].\n",
    "\n",
    "    Args:\n",
    "        n(int):         color gradations number\n",
    "        name(str):      matplotlib colormaps name\n",
    "\n",
    "    Outs:\n",
    "        np.ndarray:     [n, 4] in RGB, values in [0.0, 1.0]\n",
    "    \"\"\"\n",
    "    colormap = plt.get_cmap(name)\n",
    "    gradations = np.linspace(0, 1, n)\n",
    "    colormap_array = colormap(gradations)\n",
    "    if int_out:\n",
    "        colormap_array = np.uint8(colormap_array * 255)\n",
    "    return colormap_array"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fcc722f8-caa5-4947-bec8-36ae86b1e4bc",
   "metadata": {},
   "source": [
    "## 调试"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "f233e214-8d7c-4b99-bcdd-09e600a5496e",
   "metadata": {},
   "outputs": [],
   "source": [
    "options = webdriver.FirefoxOptions()\n",
    "driver = webdriver.Firefox(options=options)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "1970ecde-c236-42b1-9b10-cc4285973a52",
   "metadata": {},
   "outputs": [],
   "source": [
    "driver.get(\"https://store.steampowered.com/\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "7fa860c1-e13a-4b80-974e-8870a236135e",
   "metadata": {},
   "outputs": [],
   "source": [
    "lajiyouxi_meiyingwen = {\"Hero's Adventure: Road to Passion\": \"大侠立志传\"}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a0d41dc-2617-4e2c-9d23-6ae81bf63ae3",
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(len(game_names)):\n",
    "    search_game_homepage(driver, game_names[i], lajiyouxi_meiyingwen)\n",
    "\n",
    "    # 加载页面和评论区，稍等一下\n",
    "    WebDriverWait(driver, 15).until(EC.visibility_of_element_located((By.ID, \"gameHeaderImageCtn\")))\n",
    "    reviews_filter_options = driver.find_element(By.ID, \"reviews_filter_options\")\n",
    "    driver.execute_script(\"arguments[0].scrollIntoView(true);\", reviews_filter_options)\n",
    "    WebDriverWait(driver, 10).until(\n",
    "        EC.visibility_of_element_located((By.ID, \"user_reviews_filter_score\"))\n",
    "    )\n",
    "    time.sleep(2 + random.random())\n",
    "\n",
    "    # 获取简介\n",
    "    game_description = get_game_description(driver, driver.current_url.split(\"#\")[0])\n",
    "\n",
    "    # 保存元数据\n",
    "    set_game_metadata(game_names[i], game_description, ei_paths[i])\n",
    "\n",
    "    # break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "3bc26fa1-2dda-4a0f-b40f-c9eb93a3e395",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'https://howlongtobeat.com/games/Hollow_Knight_collection_branding.jpg'"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hltb[0].game_image_url"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "f25ac88b-e276-4be3-b529-b8eeaae5e2da",
   "metadata": {},
   "outputs": [],
   "source": [
    "game_names = []\n",
    "ei_paths = []\n",
    "for name in metadata_dict:\n",
    "    game_names.append(metadata_dict[name][\"howlongtobeat\"][\"game_name\"])\n",
    "    ei_paths.append(metadata_dict[name][\"EI_dataset\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 180,
   "id": "e774ab1c-0b46-4760-978d-55faebb2dad7",
   "metadata": {},
   "outputs": [],
   "source": [
    "hltb = HowLongToBeat().search(game_names[-3])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 181,
   "id": "4339ca5a-8041-4e4d-8585-6dbe6641ca5b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['VA-11 Hall-A: Cyberpunk Bartender Action']"
      ]
     },
     "execution_count": 181,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[hltb[i].game_name for i in range(len(hltb))]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0eeaeb90-fcf0-4260-8b44-44195b723749",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "baef8543",
   "metadata": {},
   "outputs": [],
   "source": [
    "options = webdriver.FirefoxOptions()\n",
    "driver = webdriver.Firefox(options=options)\n",
    "\n",
    "# 打开游戏主页\n",
    "game_link = \"https://store.steampowered.com/app/2751000/_/\" + \"?l=english\"\n",
    "driver.get(game_link)\n",
    "game_name = driver.find_element(By.ID, \"appHubAppName\").text\n",
    "\n",
    "# 切换回中文语言\n",
    "driver.execute_script(\"ChangeLanguage( 'schinese' ); return false;\")\n",
    "time.sleep(random.random() * 1 + 5)  # 网络不好这里之后可能会报错\n",
    "\n",
    "# 滚动到游戏时间菜单\n",
    "element_present = EC.presence_of_element_located((By.ID, \"reviews_filter_options\"))\n",
    "reviews_filter_options = WebDriverWait(driver, 10).until(element_present)\n",
    "driver.execute_script(\"arguments[0].scrollIntoView(true);\", reviews_filter_options)\n",
    "\n",
    "# 显示所有语言\n",
    "user_reviews_filter_menus = reviews_filter_options.find_elements(\n",
    "    By.CLASS_NAME, \"user_reviews_filter_menu\"\n",
    ")\n",
    "hover_on_element(driver, user_reviews_filter_menus[2])\n",
    "review_language_all_button = driver.find_element(By.ID, \"review_language_all\")\n",
    "driver.execute_script(\"arguments[0].click()\", review_language_all_button)\n",
    "\n",
    "time.sleep(random.random() * 1 + 5)  # 网络不好这里之后可能会报错\n",
    "\n",
    "# 获取游戏简介\n",
    "game_description = get_game_description(driver, game_link)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ca2158d-7c65-4f77-845c-aa46aca687e4",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 208,
   "id": "bb89a925-c698-44c5-9caa-f4df6c095c99",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 悬停滑条菜单\n",
    "hover_on_element(driver, user_reviews_filter_menus[4])\n",
    "\n",
    "# 定位滑条\n",
    "app_reviews_playtime_slider = driver.find_element(By.ID, \"app_reviews_playtime_slider\")\n",
    "handles = app_reviews_playtime_slider.find_elements(By.CLASS_NAME, \"ui-slider-handle\")\n",
    "left_handle = handles[0]\n",
    "right_handle = handles[1]\n",
    "\n",
    "# 每增加 1 小时游玩时长，统计一次评论数\n",
    "slider_width = app_reviews_playtime_slider.size[\"width\"]\n",
    "offsets = np.int32(np.trunc(np.linspace(0, slider_width, 101))).tolist()\n",
    "reviews_nums = [get_review_filter_score(driver)]\n",
    "reviews_hours = [0]\n",
    "for perc in range(100):\n",
    "    hour = perc + 1\n",
    "    try:\n",
    "        new_reviews_num = get_one_reviews_num(\n",
    "            driver, left_handle, hour, offsets[hour] - offsets[hour - 1]\n",
    "        )\n",
    "    except:\n",
    "        time.sleep(5)\n",
    "        hover_on_element(driver, user_reviews_filter_menus[4])\n",
    "        new_reviews_num = get_one_reviews_num(\n",
    "            driver, left_handle, hour, offsets[hour] - offsets[hour - 1]\n",
    "        )\n",
    "    reviews_nums.append(new_reviews_num)\n",
    "    reviews_hours.append(hour)\n",
    "    time.sleep(random.random() / 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 210,
   "id": "ee7dbe27-aee8-4ace-9d39-fe68d5ec334a",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(data=[reviews_hours, reviews_nums], index=[\"hour\", \"num\"]).T\n",
    "valid_file_name = convert_valid_name(game_name)\n",
    "out_file_path = \"EI_dataset/\" + valid_file_name + \"_reviews_\" + time.strftime(\"%Y%m%d\") + \".csv\"\n",
    "df.to_csv(out_file_path, index=False)\n",
    "\n",
    "set_game_metadata(game_name, game_description, out_file_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f5eef820",
   "metadata": {},
   "source": [
    "#### 平均 EI 分数计算和更新"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6ad045a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 读取 EI 数据\n",
    "game_names = list(metadata_df_t.index)\n",
    "ei_dfs = {name: pd.read_csv(metadata_df[name][\"EI_dataset\"]) for name in game_names}\n",
    "\n",
    "# 计算平均 EI 分数\n",
    "avg_ei_scores = []\n",
    "for item in ei_dfs.items():\n",
    "    avg_ei_scores.append(np.mean(item[1]['num'] / item[1]['num'][0])*100)\n",
    "\n",
    "# 更新原 df\n",
    "metadata_df_t = metadata_df_t.insert(list(metadata_df.index).index('EI_dataset')+1, \"EI_avg_scores\", avg_ei_scores)\n",
    "new_metadata_df = metadata_df_t.T\n",
    "# new_metadata_df.to_json(\"games_metadata.json\", indent=4, force_ascii=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "759550ca-163a-45d5-863f-36eda3b85baf",
   "metadata": {},
   "source": [
    "## 分析"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "d849b73d-c097-4f6b-a366-b76f24347318",
   "metadata": {},
   "outputs": [],
   "source": [
    "metadata_dict = read_json_file(\"games_metadata.json\", encoding=\"utf-8\")\n",
    "metadata_df = pd.DataFrame(metadata_dict)\n",
    "metadata_df_t = metadata_df.T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 266,
   "id": "27d173cd-e280-49fd-93f8-b7495c1cdd91",
   "metadata": {},
   "outputs": [],
   "source": [
    "# write_json_file('games_metadata.json', metadata_dict, override=True, indent=4, ensure_ascii=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df285fe9-4678-4709-b541-3fb3955ab2da",
   "metadata": {},
   "source": [
    "#### 读取数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 231,
   "id": "e2c3939c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['类银河战士恶魔城',\n",
       " '类魂系列',\n",
       " '平台游戏',\n",
       " '好评原声音轨',\n",
       " '困难',\n",
       " '2D',\n",
       " '独立',\n",
       " '单人',\n",
       " '氛围',\n",
       " '探索',\n",
       " '冒险',\n",
       " '剧情丰富',\n",
       " '手绘',\n",
       " '多结局',\n",
       " '动作',\n",
       " '黑暗奇幻',\n",
       " '开放世界',\n",
       " '可爱',\n",
       " '控制器',\n",
       " '横向滚屏']"
      ]
     },
     "execution_count": 231,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "metadata_df_t['genres']['Hollow Knight']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "48d0618e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "Index: 24 entries, name to hltb_profile_platforms\n",
      "Data columns (total 10 columns):\n",
      " #   Column                                Non-Null Count  Dtype \n",
      "---  ------                                --------------  ----- \n",
      " 0   Hollow Knight                         24 non-null     object\n",
      " 1   ENDER LILIES: Quietus of the Knights  24 non-null     object\n",
      " 2   Afterimage                            24 non-null     object\n",
      " 3   Ori and the Will of the Wisps         24 non-null     object\n",
      " 4   Ender Magnolia: Bloom in the Mist     24 non-null     object\n",
      " 5   Blasphemous                           24 non-null     object\n",
      " 6   Haak                                  24 non-null     object\n",
      " 7   Animal Well                           24 non-null     object\n",
      " 8   Rain World                            24 non-null     object\n",
      " 9   Prince of Persia The Lost Crown       24 non-null     object\n",
      "dtypes: object(10)\n",
      "memory usage: 2.6+ KB\n"
     ]
    }
   ],
   "source": [
    "genre_list = [\"类银河战士恶魔城\"]\n",
    "genre_list_exclude = []\n",
    "\n",
    "\n",
    "def filter_genres(game_genres):\n",
    "    res_include = all(False for genre in genre_list if genre not in game_genres)\n",
    "    res_exclude = all(False for genre in genre_list_exclude if genre in game_genres)\n",
    "    return res_include and res_exclude\n",
    "\n",
    "\n",
    "mask = metadata_df.loc[\"genres\"].apply(filter_genres)\n",
    "filtered_df = metadata_df.loc[:, mask]\n",
    "\n",
    "# filtered_df = metadata_df[['Death Stranding: Director\\'s Cut','Elden Ring','Baldur\\'s Gate 3','Sekiro: Shadows Die Twice',\n",
    "#                            'Persona 5 Royal','Red Dead Redemption 2','God of War Ragnarök','Ghost of Tsushima DIRECTORS CUT',\n",
    "#                            'Black Myth: Wukong','Final Fantasy VII Rebirth','Cyberpunk 2077','Lies of P','Metaphor ReFantazio']]\n",
    "# filtered_df = metadata_df[['Death Stranding: Director\\'s Cut','Elden Ring','Baldur\\'s Gate 3','Sekiro: Shadows Die Twice',\n",
    "#                            'Red Dead Redemption 2','God of War Ragnarök','Metaphor ReFantazio',\n",
    "#                            'Black Myth Wukong','Final Fantasy VII Rebirth','Cyberpunk 2077']]\n",
    "# mask = filtered_df.loc['genres'].apply(filter_genres)\n",
    "# filtered_df = filtered_df.loc[:, mask]\n",
    "# filtered_df.info()\n",
    "\n",
    "[filtered_df.pop(item) for item in [\"Tunic\", \"ASTLIBRA Revision\"]]\n",
    "\n",
    "filtered_df.info()\n",
    "\n",
    "sorted_df = filtered_df.T.sort_values(by=[\"EI_avg_scores\"]).T\n",
    "hltb_df = sorted_df.T[\n",
    "    [\"name\", \"EI_avg_scores\", \"hltb_main_story\", \"hltb_completionist\", \"hltb_main_extra\", \"hltb_all_styles\"]\n",
    "]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "621f13ff-4a2f-4927-977a-de83e6ddf3e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "metadata_df.keys()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7b40cc2d-cbc9-46f0-a2cc-10f862bc6edf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# check_names = [\"ASTLIBRA Revision\"]\n",
    "# check_set = set(metadata_df[check_names[0]][\"genres\"])\n",
    "# for name in check_names:\n",
    "#     check_set.intersection_update(metadata_df[name][\"genres\"])\n",
    "# check_set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "67570886",
   "metadata": {},
   "outputs": [],
   "source": [
    "filtered_df\n",
    "# sorted_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "3d782cf8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>name</th>\n",
       "      <th>EI_avg_scores</th>\n",
       "      <th>hltb_main_story</th>\n",
       "      <th>hltb_completionist</th>\n",
       "      <th>hltb_main_extra</th>\n",
       "      <th>hltb_all_styles</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Animal Well</th>\n",
       "      <td>ANIMAL WELL</td>\n",
       "      <td>12.377422</td>\n",
       "      <td>6.83</td>\n",
       "      <td>17.11</td>\n",
       "      <td>10.77</td>\n",
       "      <td>9.94</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Ender Magnolia: Bloom in the Mist</th>\n",
       "      <td>ENDER MAGNOLIA: Bloom in the Mist</td>\n",
       "      <td>16.126679</td>\n",
       "      <td>16.45</td>\n",
       "      <td>20.52</td>\n",
       "      <td>18.98</td>\n",
       "      <td>19.64</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Ori and the Will of the Wisps</th>\n",
       "      <td>Ori and the Will of the Wisps</td>\n",
       "      <td>17.835594</td>\n",
       "      <td>11.96</td>\n",
       "      <td>18.12</td>\n",
       "      <td>15.58</td>\n",
       "      <td>15.51</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ENDER LILIES: Quietus of the Knights</th>\n",
       "      <td>ENDER LILIES: Quietus of the Knights</td>\n",
       "      <td>18.628789</td>\n",
       "      <td>12.34</td>\n",
       "      <td>21.02</td>\n",
       "      <td>18.69</td>\n",
       "      <td>19.16</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Haak</th>\n",
       "      <td>勇敢的哈克</td>\n",
       "      <td>20.004776</td>\n",
       "      <td>10.81</td>\n",
       "      <td>24.12</td>\n",
       "      <td>23.69</td>\n",
       "      <td>21.39</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Blasphemous</th>\n",
       "      <td>Blasphemous</td>\n",
       "      <td>21.906988</td>\n",
       "      <td>14.0</td>\n",
       "      <td>29.59</td>\n",
       "      <td>19.69</td>\n",
       "      <td>19.31</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Prince of Persia The Lost Crown</th>\n",
       "      <td>波斯王子：失落的王冠</td>\n",
       "      <td>23.130183</td>\n",
       "      <td>16.59</td>\n",
       "      <td>30.86</td>\n",
       "      <td>24.14</td>\n",
       "      <td>24.6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Afterimage</th>\n",
       "      <td>心渊梦境</td>\n",
       "      <td>25.863254</td>\n",
       "      <td>24.83</td>\n",
       "      <td>42.69</td>\n",
       "      <td>34.69</td>\n",
       "      <td>35.8</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Rain World</th>\n",
       "      <td>雨世界</td>\n",
       "      <td>37.382508</td>\n",
       "      <td>20.56</td>\n",
       "      <td>67.17</td>\n",
       "      <td>31.61</td>\n",
       "      <td>29.74</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Hollow Knight</th>\n",
       "      <td>Hollow Knight</td>\n",
       "      <td>41.707422</td>\n",
       "      <td>27.01</td>\n",
       "      <td>64.48</td>\n",
       "      <td>41.69</td>\n",
       "      <td>42.04</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                                      name  \\\n",
       "Animal Well                                                    ANIMAL WELL   \n",
       "Ender Magnolia: Bloom in the Mist        ENDER MAGNOLIA: Bloom in the Mist   \n",
       "Ori and the Will of the Wisps                Ori and the Will of the Wisps   \n",
       "ENDER LILIES: Quietus of the Knights  ENDER LILIES: Quietus of the Knights   \n",
       "Haak                                                                 勇敢的哈克   \n",
       "Blasphemous                                                    Blasphemous   \n",
       "Prince of Persia The Lost Crown                                 波斯王子：失落的王冠   \n",
       "Afterimage                                                            心渊梦境   \n",
       "Rain World                                                             雨世界   \n",
       "Hollow Knight                                                Hollow Knight   \n",
       "\n",
       "                                     EI_avg_scores hltb_main_story  \\\n",
       "Animal Well                              12.377422            6.83   \n",
       "Ender Magnolia: Bloom in the Mist        16.126679           16.45   \n",
       "Ori and the Will of the Wisps            17.835594           11.96   \n",
       "ENDER LILIES: Quietus of the Knights     18.628789           12.34   \n",
       "Haak                                     20.004776           10.81   \n",
       "Blasphemous                              21.906988            14.0   \n",
       "Prince of Persia The Lost Crown          23.130183           16.59   \n",
       "Afterimage                               25.863254           24.83   \n",
       "Rain World                               37.382508           20.56   \n",
       "Hollow Knight                            41.707422           27.01   \n",
       "\n",
       "                                     hltb_completionist hltb_main_extra  \\\n",
       "Animal Well                                       17.11           10.77   \n",
       "Ender Magnolia: Bloom in the Mist                 20.52           18.98   \n",
       "Ori and the Will of the Wisps                     18.12           15.58   \n",
       "ENDER LILIES: Quietus of the Knights              21.02           18.69   \n",
       "Haak                                              24.12           23.69   \n",
       "Blasphemous                                       29.59           19.69   \n",
       "Prince of Persia The Lost Crown                   30.86           24.14   \n",
       "Afterimage                                        42.69           34.69   \n",
       "Rain World                                        67.17           31.61   \n",
       "Hollow Knight                                     64.48           41.69   \n",
       "\n",
       "                                     hltb_all_styles  \n",
       "Animal Well                                     9.94  \n",
       "Ender Magnolia: Bloom in the Mist              19.64  \n",
       "Ori and the Will of the Wisps                  15.51  \n",
       "ENDER LILIES: Quietus of the Knights           19.16  \n",
       "Haak                                           21.39  \n",
       "Blasphemous                                    19.31  \n",
       "Prince of Persia The Lost Crown                 24.6  \n",
       "Afterimage                                      35.8  \n",
       "Rain World                                     29.74  \n",
       "Hollow Knight                                  42.04  "
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "hltb_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "8df619c8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def calculate_EI_scores(ei_data_dfs, hltb_df):\n",
    "    EI_scores = {}\n",
    "    game_names = list(ei_data_dfs.keys())\n",
    "    for name in game_names:\n",
    "        ei_data = ei_data_dfs[name][\"num\"] / ei_data_dfs[name][\"num\"][0]\n",
    "        \n",
    "        sample_points = int(hltb_df['hltb_main_story'][name]), int(hltb_df['hltb_completionist'][name])\n",
    "        sample_points = [min(p, 100) for p in sample_points]\n",
    "        \n",
    "        EI_scores[name] = round(ei_data[sample_points] * 100)\n",
    "    return EI_scores"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c07e503d-5b6f-4043-bcbc-06c66a641ba8",
   "metadata": {},
   "outputs": [],
   "source": [
    "game_names = list(hltb_df.index)\n",
    "ei_dfs = {name: pd.read_csv(sorted_df[name][\"EI_dataset\"]) for name in game_names}\n",
    "EI_scores = calculate_EI_scores(ei_dfs, hltb_df)\n",
    "\n",
    "continuous_colormap = get_colormap_array(len(ei_dfs))\n",
    "\n",
    "# plt.figure(figsize=(16, 9))\n",
    "plt.figure(figsize=(10, 7))\n",
    "for i, (name, r_df) in enumerate(ei_dfs.items()):\n",
    "    plt.plot(r_df[\"hour\"], r_df[\"num\"] / r_df[\"num\"][0], \"-\", color=continuous_colormap[i]/255)\n",
    "plt.grid(\"on\")\n",
    "plt.xticks(np.arange(0, 105, 5))\n",
    "plt.yticks(np.arange(0, 1.1, 0.1))\n",
    "plt.xlabel(\"时长/小时\")\n",
    "plt.ylabel(\"留存率\")\n",
    "# plt.title(\"大作\")\n",
    "plt.title(genre_list[0])\n",
    "plt.legend(\n",
    "    [\n",
    "        f\"{metadata_dict[name]['hltb_all_styles']:.0f} \" + metadata_dict[name][\"name\"]\n",
    "        for name in game_names\n",
    "    ],\n",
    "    fontsize=\"xx-small\",\n",
    ")\n",
    "plt.savefig(\"docs/example.png\", bbox_inches=\"tight\", pad_inches=0.1)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "968cd5ef-f987-45c3-90f2-1f107c33d0d6",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pocket",
   "language": "python",
   "name": "pocket"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
